using System;
using System.Collections.Specialized;
using System.Globalization;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using MCMLXVII.BackEnd.Services.Common.Messages;

namespace MCMLXVII.Core.Common.BackEnd
{
    public enum CallContextAuthenticationType
    {         
        WindowsIdentity = 0,
        UserNameToken = 1,
        KerberosToken = 2,
        X509Token = 3
    }

    public enum ExecutionMode
    { 
        Synchronus = 0,
        BeginAsynchronus = 1,
        EndAsynchronus = 2,
        ContinueAsynchronus = 3
    }

    public class CallContext
    {

        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
            int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
            int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);

        const int LOGON32_PROVIDER_DEFAULT = 0;

        const int LOGON32_LOGON_INTERACTIVE = 2;
        const int SecurityImpersonation = 2;

        private IntPtr tokenHandle = new IntPtr(0);

        #region private variables
        private CallContextAuthenticationType _IdentityType;
        private IIdentity _Identity;
        private SecurityToken _Token;
        private Guid _SessionID;
        private bool _SessionVerified;
        private bool _IdentityAutenticated;
        private Guid _ActionGuid;
        private string _ActionName;
        private bool _ClientTrace;
        private string _Trace;
        private bool _IsInternal;
        private CultureInfo _RequestedCulture;
        private NameValueCollection _Parameters;
        private string _Evidence;
        private string _TransportName;
        private string _UserName;
        private string _UserDomain;
        private ExecutionMode _ExecutionMode;
        private System.Net.ICredentials _UserCredential;
        private WindowsImpersonationContext _ImpersonationContext;
        private bool _Impersonating;

        #endregion
        #region Properties

        public System.Net.ICredentials UserCredential
        {
            get { return _UserCredential; }
            set { _UserCredential = value; }
        }

        public bool ClientTrace
        {
            get { return _ClientTrace; }
        }
        public string TransportName
        {
            get { return _TransportName; }
        }
        public string Trace
        {
            get { return _Trace; }
            set { _Trace = value; }
        }
        public bool IsInternal
        {
            get { return _IsInternal; }
            set { _IsInternal = value; }
        }
        public IIdentity Identity
        {
            get { return _Identity; }
        }
        public SecurityToken SecurityToken
        {
            get { return _Token; }
        }
        public CallContextAuthenticationType AuthenticationType
        {
            get { return _IdentityType; }
        }
        public string Evidence
        {
            get { return _Evidence; }
        }        
        public Guid SessionID
        {
            get { return _SessionID; }
            set { _SessionID = value; }
        }

        public bool SessionVerified
        {
            get { return _SessionVerified; }
            set { _SessionVerified = value; }
        }

        public bool IdentityAutenticated
        {
            get { return _IdentityAutenticated; }
        }

        public CultureInfo RequestedCulture
        {
            get { return _RequestedCulture; }
        }
        public Guid ActionGuid
        {
            get { return _ActionGuid; }
        }
        public string ActionName
        {
            get { return _ActionName; }
        }
        public string GetParameter(string ParameterName)
        {
            return _Parameters[ParameterName];
        }

        public void SetParameter(string ParameterName, string ParameterValue)
        {
            _Parameters.Add(ParameterName, ParameterValue);
        }
        #endregion
        #region Constructors
        public CallContext()
        {
            _Parameters = new NameValueCollection();
        }        
        public CallContext(string ActionName, string TransportName, bool ClientTrace, IIdentity Identity, Guid SessionID, string Evidence, CultureInfo RequestedCulture)
        {
            if ((!Identity.IsAuthenticated) || Identity.GetType() != typeof(WindowsIdentity))
                throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"the Context constructor received an invalid Identity"));
            _IdentityType = CallContextAuthenticationType.WindowsIdentity;
            _Identity = Identity;
            _IdentityAutenticated = true;

            _Parameters = new NameValueCollection();
            _ActionGuid = Guid.NewGuid();
            _TransportName = TransportName;
            _ActionName = ActionName;
            _ClientTrace = ClientTrace;
            _IsInternal = false;            
            _SessionID = SessionID;
            _SessionVerified = false;
            _Evidence = Evidence;
            _RequestedCulture = RequestedCulture;

            _UserDomain = "";
            _UserName = "";
            _ExecutionMode = ExecutionMode.Synchronus ;
            _ImpersonationContext = null;
            _Impersonating = false;
        }
        public CallContext(string ActionName, string TransportName, bool ClientTrace, CallContextAuthenticationType SecurityTokenType,  SecurityToken Token,  Guid SessionID, string Evidence, CultureInfo RequestedCulture)
        {
            switch (SecurityTokenType)
            { 
                case CallContextAuthenticationType.KerberosToken:
                    if (Token.GetType() != typeof(KerberosReceiverSecurityToken))
                        throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"the Context constructor received an invalid Kerberos security token"));
                    _IdentityType = CallContextAuthenticationType.KerberosToken;
                    break;
                case CallContextAuthenticationType.UserNameToken:
                    if (Token.GetType() != typeof(UserNameSecurityToken))
                        throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"the Context constructor received an invalid username security token"));
                        _IdentityType = CallContextAuthenticationType.UserNameToken;
                    break;
                case CallContextAuthenticationType.X509Token:
                    if (Token.GetType() != typeof(X509SecurityToken))
                        throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"the Context constructor received an invalid X.509 security token"));
                    _IdentityType = CallContextAuthenticationType.X509Token;
                    break;
                default:
                    if (Token.GetType() != typeof(X509SecurityToken))
                        throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"the Context constructor received an invalid security token"));
                    break;
            }
            _IdentityAutenticated = false;
            _Token = Token;
            _Parameters = new NameValueCollection();
            _ActionGuid = Guid.NewGuid();
            _ActionName = ActionName;
            _TransportName = TransportName;
            _ClientTrace = ClientTrace;
            _IsInternal = false;
            _SessionID = SessionID;
            _SessionVerified = false ;
            _Evidence = Evidence;
            _RequestedCulture = RequestedCulture;
            _UserDomain = "";
            _UserName = "";
            _ExecutionMode = ExecutionMode.Synchronus;
            _ImpersonationContext = null;
            _Impersonating = false;
        }

        public string UserName
        {
            get { return _UserName; }
        }

        public string UserDomain
        {
            get { return _UserDomain; }
        }

        public void MarkUserAuthenticated(string UserName, string UserDomain)
        {
            _UserName = UserName;
            _UserDomain = UserDomain;
            _IdentityAutenticated = true;
        }

        public ExecutionMode ExecMode
        {
            get { return _ExecutionMode; }
            set { _ExecutionMode = value; }
        }

        public bool IsImpersonating()
        {
            return _Impersonating;
        }

        public void Impersonate()
        {
            switch (_IdentityType)
            { 
                case CallContextAuthenticationType.WindowsIdentity:
                    _ImpersonationContext = ((WindowsIdentity)_Identity).Impersonate();
                    _Impersonating = true;
                    break;
                case CallContextAuthenticationType.KerberosToken:
                    _ImpersonationContext = ((WindowsIdentity)((KerberosReceiverSecurityToken)_Token).WindowsIdentity).Impersonate();
                    _Impersonating = true;
                    break;
                case CallContextAuthenticationType.UserNameToken:
                    string Password = ((UserNameSecurityToken)_Token).Password;
                    tokenHandle = IntPtr.Zero;
                    bool returnValue = LogonUser(_UserName, _UserDomain, Password,
                        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
                        ref tokenHandle);
                    if (false == returnValue)
                    {
                        int ret = Marshal.GetLastWin32Error();
                        throw new System.ComponentModel.Win32Exception(ret);
                    }
                    _ImpersonationContext = new WindowsIdentity(tokenHandle).Impersonate();
                    _Impersonating = true;
                    break;
                case CallContextAuthenticationType.X509Token:
                    throw new FaultException<BackendFault>(new BackendFault(BackendFaulType.Security ,"not implemented yet"));
            }
        }

        public void UndoImpersonation()
        {
            if (_Impersonating)
            {
                _ImpersonationContext.Undo();
                _Impersonating = false;
                if (_IdentityType == CallContextAuthenticationType.UserNameToken)
                    CloseHandle(tokenHandle);
            }
        }

#endregion
    }
}
